package com.liuqi.tool.idea.plugin.codegenerator.actions;

import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.liuqi.tool.idea.plugin.codegenerator.bean.ClassDefiner;
import com.liuqi.tool.idea.plugin.codegenerator.bean.EntityField;
import com.liuqi.tool.idea.plugin.codegenerator.bean.GeneratorConfig;
import com.liuqi.tool.idea.plugin.codegenerator.utils.ClassCreator;
import com.liuqi.tool.idea.plugin.codegenerator.utils.MyStringUtils;
import com.liuqi.tool.idea.plugin.codegenerator.utils.PsiUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jdesktop.swingx.JXButton;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 实体类代码创建器
 *
 * @author LiuQi 2019/7/11-10:50
 * @version V1.0
 **/
public class GeneratorAction extends AbstractAction {
    /**
     * 工作目录
     */
    private PsiDirectory workDir;

    /**
     * 生成主配置
     */
    private GeneratorConfig config;

    /**
     * 实体类字段列表
     */
    private List<EntityField> fields;

    private final Pattern loopFieldsContentPattern = Pattern.compile("(?<=\\$\\$\\$loopFields).*?(?=\\$\\$\\$)");

    private static final Logger log = LoggerFactory.getLogger(GeneratorAction.class);

    private final List<String> systemFields = Arrays.asList("id", "createTime", "createBy", "createUser", "updateTime", "updateBy", "tenantId", "updateUser", "deleted");

    @Override
    public synchronized void actionPerformed(@NotNull AnActionEvent anActionEvent) {
        PsiClass aClass = this.getEditingClass(anActionEvent);
        if (null == aClass) {
            return;
        }

        // 加载生成配置
        config = GeneratorConfig.load(project);
        if (CollectionUtils.isEmpty(config.getClasses())) {
            this.showError("config.yaml中未配置classes清单");
            return;
        }

        // 如果有预期的注解，那么不包含该注解的类将不做处理，避免处理错误
        String expectAnnotation = config.getTableAnnotation();
        if (StringUtils.isNotBlank(expectAnnotation) && null == aClass.getAnnotation(expectAnnotation)) {
            this.showError("只能处理被" + expectAnnotation + "注解的Java类");
            return;
        }

        String entityName = aClass.getName().replace("Entity", "");

        if (StringUtils.isBlank(config.getBasePackage())) {
            // 未配置basePackage，则取当前Entity类往上两层当成basePackage;
            String packageName = psiUtils.getPackageName(aClass);
            String[] arr = packageName.split("\\.");
            StringBuilder sb = new StringBuilder();
            if (arr.length <= 2) {
                this.showError("未配置basePackage");
                return;
            }
            for (int i = 0; i < arr.length - 2; i++) {
                sb.append(arr[i]);
                if (i != arr.length - 3) {
                    sb.append(".");
                }
            }
            config.setBasePackage(sb.toString());
        }

        // 获取对应的实体中文名称
        String comment = PsiUtils.extractFirstDescriptionLine(aClass);

        // 获取工作目录，即main/java这个目录
        workDir = this.getWorkDir(aClass);

        String path = MyStringUtils.toUnderLineStr(entityName).replaceAll("_", "-");

        // 加载字段列表
        this.fields = PsiUtils.loadFields(aClass);

        // 显示配置弹窗
        this.showConfigPanel(() -> {
            // 根据配置的类列表进行类的生成
            String moduleName = config.getModuleName();

            config.getClasses().forEach(clazz -> {
                ClassDefiner definer = PsiUtils.loadClassDefiner(project, clazz);
                String cPackage = config.getBasePackage();
                if (StringUtils.isNotBlank(definer.getDir())) {
                    cPackage = cPackage + "." + definer.getDir().replaceAll("/", ".");
                    // 获取类所在目录，如果不存在则进行创建
                    PsiDirectory directory = PsiUtils.of(this.module)
                            .getOrCreateSubDirectory(workDir, cPackage);

                    String name = definer.getName().replace("$T$", entityName);
                    String content = definer.getTemplate().replace("$T$", entityName)
                            .replaceAll("\\$PATH\\$", path)
                            .replaceAll("\\$COMMENT\\$", comment)
                            .replaceAll("\\$MODULE\\$", moduleName);
                    String like = Optional.ofNullable(definer.getLike())
                            .map(str -> str.replaceAll("\\$T\\$", entityName))
                            .orElse(null);

                    // 增加注释
                    content = this.getComment(comment + Optional.ofNullable(definer.getComment()).orElse("")) + content;

                    log.info("准备生成类，名称：{}, 包：{}", name, cPackage);

                    List<String> imports = Optional.ofNullable(definer.getImports())
                            .map(list -> list.stream().map(item -> item.replaceAll("\\$T\\$", entityName))
                                    .collect(Collectors.toList())).orElse(null);

                    // 创建类并添加到模块中
                    ClassCreator classCreator = ClassCreator.of(this.module)
                            .init(name, content)
                            .importClass(imports)
                            .setConfig(config);

                    if (cPackage.endsWith("query")) {
                        // 查询对象
                        List<String> queryFields = fields.stream()
                                .filter(field -> field.getUnique() || field.getQueryEnabled())
                                .map(EntityField::getField)
                                .toList();
                        classCreator.copyFields(entityName + "Entity", queryFields);
                    } else {
                        classCreator.copyFields(like);
                    }

                    classCreator.addTo(directory);
                } else {
                    this.showError(clazz + "未配置所在包名");
                }
            });

            // 生成liquibase建表语句
            this.generateLiquibase(config, aClass, comment);

            // 生成前端界面
            this.generatePage(config, comment, entityName, path, aClass);

            // 生成MapperXml文件
            this.generateMybatisXmlFile(config, entityName, aClass);
        });
    }

    /**
     * 显示配置弹窗
     */
    private void showConfigPanel(Runnable callback) {
        String[] columns = {"编码", "名称", "备注", "列宽", "字段类型", "字段配置", "编辑器", "是否必需", "是否唯一", "默认值",
                "启用查询", "查询类型"};
        DefaultTableModel tableModel = new DefaultTableModel(columns, 0) {
            @Override
            public boolean isCellEditable(int row, int column) {
                return column >= 1;
            }

            @Override
            public Class<?> getColumnClass(int columnIndex) {
                Object value = getValueAt(0, columnIndex);
                if (null == value) {
                    return String.class;
                }
                return value.getClass();
            }
        };
        this.fields.forEach(field -> {
            if (systemFields.contains(field.getField())) {
                return;
            }

            Vector<Object> vector = new Vector<>();
            vector.add(field.getFieldUnderline());
            vector.add(Optional.ofNullable(field.getComment()).orElse(""));

            // 备注
            vector.add(Optional.ofNullable(field.getComment()).orElse(""));
            // 列宽，数字时默认为100px，Boolean类型默认100px，日期默认110px，日期时间默认150px
            String type = field.getType();
            switch (type) {
                case "integer", "numeric", "int", "bit" -> vector.add("100px");
                case "date" -> vector.add("110px");
                case "datetime" -> vector.add("150px");
                default -> vector.add("");
            }

            // 字段类型
            vector.add(type);
            vector.add(field.getConfig());

            // 编辑器
            if ("date".equals(type)) {
                vector.add("datePicker");
            } else if ("bit".equals(type)) {
                vector.add("checkbox");
            } else if (List.of("int", "integer", "numeric").contains(type)) {
                vector.add("number");
            } else {
                vector.add("input");
            }

            vector.add(Optional.ofNullable(field.getRequired()).orElse(false));
            vector.add(field.getUnique());
            vector.add(Optional.ofNullable(field.getDefaultValue()).orElse(""));
            vector.add(field.getQueryEnabled());
            vector.add(field.getQueryType());

            tableModel.addRow(vector);
        });
        JTable table = new JTable(tableModel);
        table.getTableHeader().setReorderingAllowed(false);
        table.getTableHeader().setResizingAllowed(true);
        JScrollPane scrollPane = new JScrollPane(table);

        BiConsumer<Integer, Integer> setWidth = (idx, width) -> {
            TableColumn column = table.getColumnModel().getColumn(idx);
            column.setMinWidth(width);
            column.setMaxWidth(width);
        };

        setWidth.accept(0, 120);
        setWidth.accept(1, 120);
        table.getColumnModel().getColumn(2).setPreferredWidth(100);
        setWidth.accept(3, 80);

        // 列类型
        setWidth.accept(4, 100);
        JComboBox<String> typeComboBox = new JComboBox<>();
        typeComboBox.addItem("varchar");
        typeComboBox.addItem("integer");
        typeComboBox.addItem("numeric");
        typeComboBox.addItem("bigint");
        typeComboBox.addItem("int");
        typeComboBox.addItem("bit");
        typeComboBox.addItem("text");
        typeComboBox.addItem("date");
        typeComboBox.addItem("datetime");
        typeComboBox.addItem("timestamp");
        table.getColumnModel().getColumn(4).setCellEditor(new DefaultCellEditor(typeComboBox));
        setWidth.accept(5, 120);

        // 编辑类型
        setWidth.accept(6, 100);
        JComboBox<String> editorTypes = new JComboBox<>();
        editorTypes.addItem("input");
        editorTypes.addItem("textarea");
        editorTypes.addItem("number");
        editorTypes.addItem("select");
        editorTypes.addItem("checkbox");
        editorTypes.addItem("radioGroup");
        editorTypes.addItem("datePicker");
        table.getColumnModel().getColumn(6).setCellEditor(new DefaultCellEditor(editorTypes));

        setWidth.accept(7, 80);
        setWidth.accept(8, 80);
        setWidth.accept(9, 120);
        setWidth.accept(10, 80);

        // 查询类型
        setWidth.accept(11, 100);
        JComboBox<String> queryTypes = new JComboBox<>();
        queryTypes.addItem("eq");
        queryTypes.addItem("neq");
        queryTypes.addItem("like");
        queryTypes.addItem("isNull");
        queryTypes.addItem("notNull");
        queryTypes.addItem("le");
        queryTypes.addItem("ge");
        table.getColumnModel().getColumn(11).setCellEditor(new DefaultCellEditor(queryTypes));

        scrollPane.setPreferredSize(new Dimension(1400, 600));

        JPanel panel = new JPanel(new BorderLayout());
        panel.add(scrollPane, BorderLayout.CENTER);

        JBPopup popup = JBPopupFactory.getInstance()
                .createComponentPopupBuilder(panel, null)
                .setTitle("字段配置")
                .setMovable(true)
                .setResizable(true)
                .setRequestFocus(true)
                .setCancelOnClickOutside(false)
                .setCancelOnWindowDeactivation(false)
                .createPopup();

        JXButton button = new JXButton(new javax.swing.AbstractAction("确定") {
            @Override
            public void actionPerformed(ActionEvent e) {
                new Task.Modal(project, "代码生成中", true) {
                    @Override
                    public void run(@NotNull ProgressIndicator progressIndicator) {
                        for (int i = 0; i < tableModel.getDataVector().size(); i++) {
                            EntityField field = fields.get(i);
                            Vector<?> data = tableModel.getDataVector().get(i);
                            // {"编码", "名称",  "备注", "列宽", "字段类型", "字段配置", "编辑器", "编辑器配置", "是否必需", "是否唯一", "默认值",
                            //                "启用查询", "查询类型"};
                            field.setName((String) data.get(1));
                            field.setComment((String) data.get(2));
                            field.setWidth((String) data.get(3));
                            field.setType((String) data.get(4));
                            field.setConfig((String) data.get(5))
                                    .setEditor((String) data.get(6))
                                    .setRequired((Boolean) data.get(7))
                                    .setUnique((Boolean) data.get(8))
                                    .setDefaultValue((String) data.get(9))
                                    .setQueryEnabled((Boolean) data.get(10))
                                    .setQueryType((String) data.get(11));
                        }

                        WriteCommandAction.runWriteCommandAction(project, callback);

                        ApplicationManager.getApplication()
                                .invokeLater(() -> {
                                    popup.closeOk(null);
                                });
                    }
                }.queue();
            }
        });
        button.setOpaque(true);
        button.setForeground(Color.white);
        JPanel buttons = new JPanel();
        buttons.add(new JButton(new javax.swing.AbstractAction("取消") {
            @Override
            public void actionPerformed(ActionEvent e) {
                ApplicationManager.getApplication()
                        .invokeLater(() -> popup.closeOk(null));
            }
        }));
        buttons.add(button);
        panel.add(buttons, BorderLayout.SOUTH);

        popup.showCenteredInCurrentWindow(editor.getProject());
    }

    /**
     * 生成mapper.xml文件
     */
    private void generateMybatisXmlFile(GeneratorConfig config, String entityName, PsiClass aClass) {
        boolean b = config.getWithMapperXml();
        if (!b) {
            return;
        }

        // 获取模板内容
        ClassDefiner definer = PsiUtils.loadClassDefiner(project, "mapperXml");
        String template = definer.getTemplate();
        if (StringUtils.isBlank(template)) {
            return;
        }

        String dir = Optional.ofNullable(definer.getDir()).orElse("mapper");
        String fileName = entityName + "Mapper.xml";
        String filePath = dir + "/" + fileName;
        if (null != psiUtils.getResourceFile(filePath)) {
            return;
        }

        String tableName = this.getTableName(config, aClass);
        template = template.replaceAll("\\$T\\$", entityName)
                .replaceAll("\\$PKG\\$", config.getBasePackage())
                .replaceAll("\\$TABLE_NAME\\$", tableName);

        // 替换列与resultMap
        StringBuilder resultMapStr = new StringBuilder();
        String columns = fields.stream()
                .peek(field -> {
                    resultMapStr.append("<result column=\"")
                            .append(field.getFieldUnderline())
                            .append("\" property=\"")
                            .append(field.getField())
                            .append("\"/>\n");
                })
                .map(EntityField::getFieldUnderline)
                .map(str -> str + "\n")
                .reduce((s1, s2) -> s1.concat(",").concat(s2))
                .orElse("");
        template = template.replaceAll("\\$COLUMNS\\$", columns);
        template = template.replace("$RESULT_MAP$", resultMapStr.toString());


        // 保存文件
        psiUtils.saveResourceFile(filePath, template);
    }

    /**
     * 生成前端界面
     *
     * @param config 生成配置
     */
    private void generatePage(GeneratorConfig config, String comment, String entityName, String path, PsiClass aClass) {
        // 加载前端生成配置文件
        String pageTemplateFile = config.getPageTemplate();
        if (StringUtils.isBlank(pageTemplateFile)) {
            return;
        }

        String template = PsiUtils.loadFileContent(project, "page.txt");
        if (StringUtils.isBlank(template)) {
            return;
        }

        String dir = "vue";
        entityName = entityName.substring(0, 1).toLowerCase(Locale.ROOT) + entityName.substring(1);
        String fileName = MyStringUtils.toUnderLineStr(entityName).replaceAll("_", "-");
        String filePath = dir + "/" + fileName + ".vue";
        if (null != psiUtils.getResourceFile(filePath)) {
            return;
        }

        template = template.replaceAll("\\$COMMENT\\$", comment)
                .replaceAll("\\$PATH\\$", path)
                .replaceAll("\\$MODULE\\$", config.getModuleName());

        // 替换$$$loopFields内容，先获取其中内容，再根据字段列表进行替换处理
        List<MatchResult> results = loopFieldsContentPattern.matcher(template).results().toList();
        if (CollectionUtils.isNotEmpty(results)) {
            // 只处理第一个
            String group = results.get(0).group();

            // group中包含$时，替换会出问题，因此先转成__
            group = group.replaceAll("\\$", "__");

            StringBuilder sb = new StringBuilder();
            for (EntityField field : fields) {
                if (field.getSystem()) {
                    continue;
                }

                String name = field.getComment();
                sb.append("\t");
                String fieldStr = group.replace("__FIELD_NAME__", name)
                        .replace("__FIELD_PROP__", field.getField());
                if (field.getConfig() != null && field.getType().equals("varchar")) {
                    fieldStr = fieldStr.replace("__FIELD_MAX__", ", max: " + Optional.ofNullable(field.getConfig()).orElse("255"));
                } else {
                    fieldStr = fieldStr.replace("__FIELD_MAX__", "");
                }

                // 字段其它配置
                String fieldConfig = "";
                if (field.getRequired()) {
                    fieldConfig += ", required: true ";
                }
                if (field.getUnique()) {
                    fieldConfig += ", unique: true ";
                }

                if (StringUtils.isNotBlank(field.getEditor())) {
                    fieldConfig += ", type: \"" + field.getEditor() + "\"";
                }
                fieldStr = fieldStr.replace("__FIELD_CONFIG__", fieldConfig);

                sb.append(fieldStr)
                        .append("\n");
            }

            if (StringUtils.isNotBlank(sb.toString())) {
                template = template.replaceAll("\\$\\$\\$loopFields.*\\$\\$\\$", sb.substring(0, sb.length() - 1));
            } else {
                template = template.replaceAll("\\$\\$\\$loopFields.*\\$\\$\\$", "");
            }
        }

        // 替换查询字段
        StringBuilder queryStr = new StringBuilder();
        for (EntityField field : fields) {
            if (field.getQueryEnabled()) {
                queryStr.append("{label: \"")
                        .append(field.getName())
                        .append("\", prop: \"")
                        .append(field.getFieldUnderline())
                        .append("\", type: \"")
                        .append(field.getEditor())
                        .append("\" },");
            }
        }
        template = template.replaceAll("\\$QUERY_FIELDS\\$", queryStr.toString());

        // 保存内容
        psiUtils.saveResourceFile(filePath, template);
    }

    /**
     * 生成Liquibase建表语句
     */
    private void generateLiquibase(GeneratorConfig config, PsiClass aClass, String tableComment) {
        if (!config.getWithLiquibase()) {
            return;
        }

        // 根据字段生成建表语句，表名从Table或者TableName中获取
        String tableName = getTableName(config, aClass);
        StringBuilder sb = new StringBuilder();
        sb.append("\ncreate table ")
                .append(tableName)
                .append("(\n");

        // id特殊处理，并放在第一个
        PsiField idField = aClass.findFieldByName("id", true);
        if (null != idField) {
            PsiType type = idField.getType();
            if (type.getCanonicalText().contains("Long")) {
                sb.append("\tid bigint not null primary key auto_increment comment '主键',\n");
            } else {
                sb.append("\tid varchar(64) not null primary key comment '主键', \n");
            }
        }

        // 处理剩余字段
        for (int i = 0; i < fields.size(); i++) {
            EntityField field = fields.get(i);
            String name = field.getFieldUnderline();
            if (name.equals("id")) {
                continue;
            }

            sb.append("\t").append(name).append(" ")
                    .append(field.getType());
            if (!StringUtils.equals(field.getType(), "text") && StringUtils.isNotBlank(field.getConfig())) {
                sb.append("(").append(field.getConfig()).append(")");
            }

            if (null != field.getRequired() && field.getRequired()) {
                sb.append(" not null");
            }

            if (StringUtils.isNotBlank(field.getDefaultValue())) {
                sb.append(" default ").append(field.getDefaultValue());
            }
            if (field.getType().equals("datetime")) {
                if (name.contains("update") || name.contains("modify")) {
                    sb.append(" on update current_timestamp");
                }
            }

            // 获取备注信息
            String comment = field.getComment();
            if (StringUtils.isNotBlank(comment)) {
                sb.append(" comment '").append(comment).append("'");
            }

            if (i != fields.size() - 1) {
                sb.append(",\n");
            }
        }
        sb.append("\n)");

        // 补充注释 
        if (StringUtils.isNotBlank(tableComment)) {
            sb.append(" comment '").append(tableComment)
                    .append("'");
        }

        // 写到liquibase文件中去
        String liquibaseFile = config.getLiquibaseFile();
        String fileName = liquibaseFile.substring(liquibaseFile.lastIndexOf(File.separator) + 1);
        PsiDirectory directory = psiUtils.getResourceDir(liquibaseFile);
        PsiFile psiFile = psiUtils.getOrCreateResourceFile(liquibaseFile);
        XmlFile file = (XmlFile) psiFile;
        XmlDocument document = file.getDocument();
        if (null == document) {
            // 无内容，需要增加内容
            String content = "<databaseChangeLog\n" +
                    "        xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
                    "        xmlns=\"http://www.liquibase.org/xml/ns/dbchangelog\"\n" +
                    "        xsi:schemaLocation=\"http://www.liquibase.org/xml/ns/dbchangelog\n" +
                    "        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd\">";
            content += "<changeSet id=\"init-user\" author=\"test\">\n" +
                    "        <sql>";
            content += sb + "</sql></changeSet></databaseChangeLog>";
            PsiFile resultFile = PsiFileFactory.getInstance(this.project)
                    .createFileFromText(fileName, XMLLanguage.INSTANCE, content);
            psiUtils.format(resultFile);
            directory.add(resultFile);
        } else if (!psiFile.getText().contains("create-table-" + tableName + "\"")) {
            XmlTag rootTag = document.getRootTag();
            XmlTag changeSet = rootTag.createChildTag("changeSet", null, null, false);
            changeSet.setAttribute("id", "create-table-" + tableName);
            changeSet.setAttribute("author", "codeGenerator");
            XmlTag sqlTag = changeSet.createChildTag("sql", null, sb.toString(), false);
            sqlTag.getValue().setText(sb.toString());
            changeSet.add(sqlTag);
            WriteCommandAction.runWriteCommandAction(project, () -> {
                rootTag.add(changeSet);
                psiUtils.format(psiFile);
                psiFile.delete();
                directory.add(psiFile);
            });
        }
    }

    /**
     * 解析表名
     *
     * @param config 配置
     * @param aClass 实体类
     * @return 表名
     */
    private @NotNull String getTableName(GeneratorConfig config, PsiClass aClass) {
        return psiUtils.getAnnotationValue(aClass.getAnnotation(config.getTableAnnotation()), "value")
                .orElseGet(() -> {
                    String name = aClass.getName().replace("Entity", "");
                    return MyStringUtils.toUnderLineStr(name);
                });
    }

    /**
     * 获取工作目录，即main/java目录
     */
    private PsiDirectory getWorkDir(PsiClass aClass) {
        PsiDirectory directory = aClass.getContainingFile().getContainingDirectory();
        while (!directory.getName().equals("java")) {
            directory = directory.getParentDirectory();
        }

        return directory;
    }

    /**
     * 组装类注释
     *
     * @param cName 中文名称
     * @return 类注释内容
     */
    private String getComment(String cName) {
        return "/** \n * " + cName + " \n * @author Coder Generator"
                + " " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + " \n**/\n";
    }
}
